package render;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;

import javax.imageio.ImageIO;
import javax.media.j3d.AmbientLight;
import javax.media.j3d.Appearance;
import javax.media.j3d.Background;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Bounds;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.ImageComponent;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.Light;
import javax.media.j3d.Material;
import javax.media.j3d.Node;
import javax.media.j3d.PointArray;
import javax.media.j3d.PointAttributes;
import javax.media.j3d.PointLight;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.SceneGraphPath;
import javax.media.j3d.Screen3D;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.View;
import javax.vecmath.AxisAngle4f;
import javax.vecmath.Color3f;
import javax.vecmath.Matrix3d;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import kernel.Fiber;
import kernel.FiberSetLite;
import kernel.Volume;

import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.geometry.GeometryInfo;
import com.sun.j3d.utils.geometry.NormalGenerator;
import com.sun.j3d.utils.picking.PickCanvas;
import com.sun.j3d.utils.picking.PickResult;
import com.sun.j3d.utils.universe.MultiTransformGroup;
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.ViewingPlatform;


public class AsuvizRenderer extends Observable implements KeyListener, MouseListener{

	private final SimpleUniverse universe;
	private BranchGroup groupRoot;
	private Map<String, BranchGroup> listBgFiberSet;
	private Map<String, BranchGroup> listBgVolumes;
	private BranchGroup groupLighting;
	private BranchGroup groupExtra;
	private BranchGroup groupBackground;
	private BranchGroup groupFibers;
	private BranchGroup groupROIMatrix;
	private BranchGroup groupVolumes;
	

	private Background backgroundNode;
	private OrbitBehavior orbitBehavior;
	private Canvas3D canvas;
	private PickCanvas pickCanvas;
	private boolean renderAxis = false;

	private boolean firstTime = true;
	private boolean lightsInitialized = false;
	private double axisScale;
	private int gridResolution = 1000;

	private boolean reverseTopView = false;
	private boolean reverseSideView = false;
	private boolean reverseFrontView = false;

	private static final int FRONT_VIEW = 0;
	private static final int SIDE_VIEW = 1;
	private static final int TOP_VIEW = 2;

	private static final String GROUP_ROOT = "GROUP_ROOT";
	private static final String GROUP_LIGHTING = "GROUP_LIGHTING";
	private static final String GROUP_EXTRA = "GROUP_EXTRA";
	private static final String GROUP_BACKGROUND = "GROUP_BACKGROUND";
	private static final String OFFSCREEN_CANVAS_NAME = "OFFSCREEN_CANVAS";
	private static final String FIBER_ = "FIBER_";
	

	public static final String GROUP_FIBERS = "GROUP_FIBERS";
	public static final String GROUP_FIBER_SET_ = "GROUP_FIBER_SET_";
	private static final Object GROUP_ROI_MATRIX = "GROUP_ROI_MATRIX";
	private static final String NODE_LIGHT_ = "NODE_LIGHT_";
	private static final String NODE_BACKGROUND = "NODE_BACKGROUND";
	private static final String GROUP_VOLUMES = "GROUP_VOLUMES";

	private static final Color3f BACKGROUND_COLOR = new Color3f(Color.LIGHT_GRAY);

	private Map<String, Light> lights = new HashMap<String, Light>();


	private double frontClipDelta = 0.00002;
	//	private double frontClipDelta = 0.000002;
	private double backClipDelta = 0.005;
	private double frontClip = 0.01;
	private double backClip = 100;

	private GraphicsConfiguration graphicsConfiguration;

	private LightingSetup lightingSetup;
	
	
	public LightingSetup getLightingSetup()
	{
		return lightingSetup;
	}

	public void setLightingSetup(String pluginName)
	{
		Class<?> resource;
		try {
			resource = Class.forName(pluginName);			
			lightingSetup = (LightingSetup)resource.newInstance();
			System.err.println("Using lighting setup: " + pluginName);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}


	/**
	 * Constructor
	 * @param canvas canvas 3D
	 */
	public AsuvizRenderer(Canvas3D canvas)
	{
		this.canvas = canvas;
		universe = new SimpleUniverse(this.canvas);
		initRenderer();
	}

	private void initRenderer()
	{

		universe.getViewer().getView().setTransparencySortingPolicy(View.TRANSPARENCY_SORT_GEOMETRY);
		universe.getViewer().getView().setDepthBufferFreezeTransparent(false);
		//		graphics.setSceneAntialiasing(true);


		//Root branch
		groupRoot = new BranchGroup();
		NodeCharacteristics.setBranchGroupCapabilities(groupRoot);
		groupRoot.setUserData(GROUP_ROOT);

		//lighting branch
		groupLighting = new BranchGroup();
		NodeCharacteristics.setBranchGroupCapabilities(groupLighting);
		groupLighting.setUserData(GROUP_LIGHTING);


		//Extra branch
		groupExtra = new BranchGroup();
		NodeCharacteristics.setBranchGroupCapabilities(groupExtra);
		groupExtra.setUserData(GROUP_EXTRA);

		//Fibers Set Group
		groupFibers = new BranchGroup();
		NodeCharacteristics.setBranchGroupCapabilities(groupFibers);
		groupFibers.setUserData(GROUP_FIBERS);
		
		groupROIMatrix = new BranchGroup();
		NodeCharacteristics.setBranchGroupCapabilities(groupROIMatrix);
		groupROIMatrix.setUserData(GROUP_ROI_MATRIX);
		
		groupVolumes = new BranchGroup();
		NodeCharacteristics.setBranchGroupCapabilities(groupVolumes);
		groupVolumes.setUserData(GROUP_VOLUMES);
		
		groupRoot.addChild(groupLighting);
		groupRoot.addChild(groupExtra);
		groupRoot.addChild(groupFibers);
		groupRoot.addChild(groupROIMatrix);
		groupRoot.addChild(groupVolumes);
		
		listBgFiberSet = new HashMap<String, BranchGroup>();
		listBgVolumes = new HashMap<String, BranchGroup>();
		
		canvas.addKeyListener(this);
		canvas.addMouseListener(this);
		universe.getViewingPlatform().setNominalViewingTransform();	

		universe.addBranchGraph(groupRoot);

	}

	public void render()
	{
		initializeOrbitBehavior(canvas);
		initializeLighting(groupRoot.getBounds());
	}

	private void initializeLighting(Bounds lightBounds) {

		if(lightingSetup != null)
		{
			lightingSetup.setup(lightBounds);
			System.err.println("initializing " + lightingSetup.getClass().getCanonicalName());
			Map<String, Light> lightsFromSetup = lightingSetup.getLights();
			for(Map.Entry<String, Light> entry : lightsFromSetup.entrySet())
			{
				addLight(entry.getKey(), entry.getValue());
			}
		}
		else
		{
			Color3f light1Color = new Color3f(1f, 1f, 1f);
			BoundingSphere bounds = (BoundingSphere) lightBounds;
			Vector3f light1Direction = new Vector3f(0.0f, 0.0f, -1.0f);
			DirectionalLight light1 = new DirectionalLight(light1Color, light1Direction);
			light1.setInfluencingBounds(bounds);
			light1.setCapability(Node.ALLOW_BOUNDS_READ);
			addLight(NODE_LIGHT_ + "0", light1);


			//		Color3f light2Color = new Color3f(1f, 1f, 1f);
			//		Vector3f light2Direction = new Vector3f(1.0f, -1.0f, 1.0f);
			//		DirectionalLight light2 = new DirectionalLight(light2Color, light2Direction);
			//		light2.setInfluencingBounds(bounds);
			//		light2.setCapability(Node.ALLOW_BOUNDS_READ);
			//		addLight(NODE_LIGHT_ + "1", light2);

			Color3f light2Color = new Color3f(1f, 1f, 1f);
			Point3f light2Attenuation = new Point3f(-1.0f, 0.0f, 0.0f);
			Point3f light2Position = new Point3f();
			PointLight light2 = new PointLight(light2Color, light2Position, light2Attenuation);
			//		light2.setInfluencingBounds(bounds);
			light2.setCapability(Node.ALLOW_BOUNDS_READ);
			//		light2.setCapability(Node.ALLOW_INBOUNDS_WRITE);
			light2.setCapability(PointLight.ALLOW_STATE_READ);
			light2.setCapability(PointLight.ALLOW_STATE_WRITE);
			light2.setCapability(PointLight.ALLOW_POSITION_READ);
			light2.setCapability(PointLight.ALLOW_POSITION_WRITE);
			light2.setCapability(PointLight.ALLOW_INFLUENCING_BOUNDS_READ);
			light2.setCapability(PointLight.ALLOW_INFLUENCING_BOUNDS_WRITE);
			//		addLight(NODE_LIGHT_ + "2", light2);

			Color3f light3Color = new Color3f(1f, 1f, 1f);
			Vector3f light3Direction = new Vector3f(0.0f, 0.0f, 1.0f);
			DirectionalLight light3 = new DirectionalLight(light3Color, light3Direction);
			light3.setInfluencingBounds(bounds);
			light3.setCapability(Node.ALLOW_BOUNDS_READ);
			addLight(NODE_LIGHT_ + "3", light3);


			Color3f light4Color = new Color3f(1f, 1f, 1f);
			Vector3f light4Direction = new Vector3f(0.0f, 0.0f, -1.0f);
			DirectionalLight light4 = new DirectionalLight(light4Color, light4Direction);
			light4.setInfluencingBounds(bounds);
			light4.setCapability(Node.ALLOW_BOUNDS_READ);
			//addLight(NODE_LIGHT_ + "4", light4);

			Color3f light5Color = new Color3f(0.2f, 0.2f, 0.2f);
			AmbientLight light5 = new AmbientLight(light5Color);
			light5.setInfluencingBounds(bounds);
			light5.setCapability(Node.ALLOW_BOUNDS_READ);
			//		addLight(NODE_LIGHT_ + "5", light5);
		}
	}

	private void updateLighting(Bounds lightBounds)
	{
		if(!lightsInitialized)
		{
			initializeLighting(lightBounds);
			lightsInitialized = true;
		}

		if(lightingSetup != null)
		{
			lightingSetup.updateLights(lightBounds);
			System.err.println("updating " + lightingSetup.getClass().getCanonicalName());
			BranchGroup lightingPoints = lightingSetup.renderPointLights();
			if(lightingPoints.numChildren() > 0)
			{
				groupExtra.addChild(lightingPoints);
			}
		}
		else
		{
			BoundingSphere bounds = (BoundingSphere) lightBounds;
			Point3d boundsCenter = new Point3d();
			bounds.getCenter(boundsCenter);
			double boundsRadius = bounds.getRadius();

			double boundsReach = bounds.getRadius();
			bounds.setRadius(boundsReach * 20);

			for(Map.Entry<String, Light> lightEntry : lights.entrySet())
			{
				Light light = lightEntry.getValue();

				if(light instanceof PointLight)
				{
					light.setInfluencingBounds(bounds);
					//				Point3f newPosition = new Point3f((float)boundsCenter.x , (float)boundsCenter.y , (float)boundsCenter.z + (float)boundsRadius);
					Point3f newPosition = new Point3f((float)boundsCenter.x + (float)(boundsRadius * (Math.cos(Math.toRadians(-30)))), (float)boundsCenter.y  + (float)(boundsRadius * (Math.sin(Math.toRadians(-30)))), (float)boundsCenter.z + (float)boundsRadius);
					((PointLight) light).setPosition(newPosition);
					light.setEnable(true);

					/* RENDER LIGHT POSITION
				Sphere lightPosition = new Sphere(1000);
				BranchGroup lightPositionBG = new BranchGroup();
				TransformGroup lightPositionTG = new TransformGroup();
				Transform3D lightPositionT3d = new Transform3D();
				lightPositionT3d.setTranslation(new Vector3d(newPosition.x, newPosition.y, newPosition.z));
				lightPositionTG.setTransform(lightPositionT3d);
				lightPositionTG.addChild(lightPosition);
				lightPositionBG.addChild(lightPositionTG);
				groupRoot.addChild(lightPositionBG);
				//*/

				}
			}
		}
	}

	/**
	 * Adds a light in the array of lights for the scene
	 * @param name name of the light
	 * @param light light object
	 */
	private void addLight(String name, Light light)
	{
		light.setUserData(name);
		lights.put(name, light);
		groupLighting.detach();
		groupLighting.addChild(light);
		groupRoot.addChild(groupLighting);
	}

	private void updateBounds(Bounds bounds)
	{
		BoundingSphere sceneBounds = (BoundingSphere)bounds;
		double boundsReach = sceneBounds.getRadius() * 20;
		sceneBounds.setRadius(boundsReach * 20);

		groupLighting.detach();
		for(Map.Entry<String, Light> entry : lights.entrySet())
		{
			Light l = entry.getValue();
			l.setInfluencingBounds(sceneBounds);
		}
		groupRoot.addChild(groupLighting);

		groupBackground.detach();
		backgroundNode.setApplicationBounds(sceneBounds);
		groupRoot.addChild(groupBackground);

	}

	/**
	 * Prepares the renderer to render an axis
	 * @param renderAxis if the axis is rendered
	 * @param gridResolution resolution for the grid that is rendered along with the axis
	 */
	public void renderAxis( Bounds bounds)
	{
		BoundingSphere bSphere = (BoundingSphere)bounds;
		int scale = (int) bSphere.getRadius();
		//		this.renderAxis = renderAxis;
		if(renderAxis)
		{
			groupExtra.detach();

			//		
			groupExtra.addChild(UtilitaryMethods.createAxis(scale));
			//*/
			groupRoot.addChild(groupExtra);
		}
		else
		{

			if(groupExtra.numChildren() > 0)
			{
				groupExtra.detach();
				groupExtra.removeChild(0);
				groupRoot.addChild(groupExtra);
			}

		}
	}



//	private BranchGroup getMarkerByName(String name)
//	{
//		BranchGroup bg = null;
//		Enumeration children = groupGait.getAllChildren();
//		while(children.hasMoreElements())
//		{
//			bg = (BranchGroup) children.nextElement();
//			if(((String)bg.getUserData()).equalsIgnoreCase(name))
//			{
//				break;
//			}
//			else
//			{
//				bg = null;
//			}
//		}
//
//		return bg;
//	}

//	/**
//	 * Renders a string over a visual object inside the scene graph (font 
//	 * @param offset distance between the center of the object and teh left lower corner of the text
//	 * @param size size of the text in visual units
//	 * @param name visual node name
//	 * @param nameToRender string to be rendered
//	 * @param textColor color of the text
//	 */
//	public void addMarkerName(double offset, double size, String name, String nameToRender, Color textColor)
//	{
//		BranchGroup bgNode = getMarkerByName(name);
//		if(bgNode != null)
//		{
//			Node child = bgNode.getChild(0);
//			if(child instanceof TransformGroup)
//			{
//				BoundingSphere bounds = (BoundingSphere)groupRoot.getBounds();
//				bounds.setRadius(bounds.getRadius() * 100);
//				float[] textColorValues = new float[3];
//				textColor.getRGBColorComponents(textColorValues);
//				((TransformGroup) child).addChild(NodeCharacteristics.createTextBranchGroup( bounds, offset, size, nameToRender, new Color3f(textColorValues[0], textColorValues[1], textColorValues[2])));
//			}
//		}
//		else
//		{
//			System.err.println("Could not render name " + nameToRender + " to node " + name + ". Node does not exist");
//		}
//	}



	/**
	 * Initialize the Orbit behavior
	 * @param canvas renderer's canvas 3D
	 */
	private void initializeOrbitBehavior(Canvas3D canvas)
	{
		if(canvas != null)
		{
			orbitBehavior = new OrbitBehavior(canvas, OrbitBehavior.REVERSE_ALL | OrbitBehavior.PROPORTIONAL_ZOOM);
		}
		else
		{
			orbitBehavior = new OrbitBehavior();
			orbitBehavior.setCapability(OrbitBehavior.STOP_ZOOM);
		}
	}

	/**
	 * Setup the orbit behavior. Should be called when the scene is ready.
	 * by default sets the top view
	 */
	private void updateOrbitBehavior(Bounds newBounds)
	{
		BoundingSphere boundingSphere = (BoundingSphere) newBounds;
		float boundingRadius = (float) boundingSphere.getRadius(); 
		Point3d boundingCenter = new Point3d();
		boundingSphere.getCenter(boundingCenter);

		BoundingSphere bounds = new BoundingSphere(boundingCenter, boundingRadius * 20);
		orbitBehavior.setSchedulingBounds(bounds);

		ViewingPlatform viewingPlatform = universe.getViewingPlatform();
		viewingPlatform.setViewPlatformBehavior(orbitBehavior);
	}

	private void setView(int viewPosition)
	{
		ViewingPlatform viewingPlatform = universe.getViewingPlatform();

		//Get the transform group of the viewing platform
		TransformGroup vpTransformGroup = viewingPlatform.getViewPlatformTransform();
		Transform3D vpTransform3D = new Transform3D();	
		Vector3f transVector = new Vector3f();

		//Get the transformation vector from the transform3d of the transform group
		vpTransformGroup.getTransform(vpTransform3D);   
		vpTransform3D.get(transVector);

		BoundingSphere scenarioBounds = (BoundingSphere) groupRoot.getBounds();
		float radiusBS = (float) scenarioBounds.getRadius();
		Point3d boundsCenter = new Point3d();
		scenarioBounds.getCenter(boundsCenter);

		switch (viewPosition)
		{
		case TOP_VIEW:
			AxisAngle4f cameraRotationTop = null;
			if(reverseTopView)
			{
				transVector.x = (float) boundsCenter.x;
				transVector.y = (float) boundsCenter.y;
				transVector.z = (float) boundsCenter.z - (radiusBS * 2.5f);
				vpTransform3D.setTranslation(transVector);
				cameraRotationTop = new AxisAngle4f(new Vector3f(0, 1, 0), (float)Math.toRadians(180));
			}
			else
			{
				transVector.x = (float) boundsCenter.x;
				transVector.y = (float) boundsCenter.y;
				transVector.z = (float) boundsCenter.z + (radiusBS * 2.5f);
				vpTransform3D.setTranslation(transVector);
				cameraRotationTop = new AxisAngle4f(new Vector3f(0, 1, 0), (float)Math.toRadians(0));
			}

			vpTransform3D.setRotation(cameraRotationTop);
			break;
		case SIDE_VIEW:
			Matrix3d cameraRotationSide = null;
			if(reverseSideView)
			{
				transVector.x = (float) boundsCenter.x - (radiusBS * 2.5f);
				transVector.y = (float) boundsCenter.y;
				transVector.z = (float) boundsCenter.z;

				vpTransform3D.setTranslation(transVector);

				cameraRotationSide = new Matrix3d();
				Transform3D yRotationSide = new Transform3D();
				yRotationSide.rotY((float)Math.toRadians(-90));
				yRotationSide.get(cameraRotationSide);
//				Transform3D xRotationSide = new Transform3D();
//				xRotationSide.rotX((float)Math.toRadians(0));
//				xRotationSide.mul(yRotationSide);
//				xRotationSide.get(cameraRotationSide);
			}
			else
			{
				transVector.x = (float) boundsCenter.x + (radiusBS * 2.5f);
				transVector.y = (float) boundsCenter.y;
				transVector.z = (float) boundsCenter.z;

				vpTransform3D.setTranslation(transVector);

				cameraRotationSide = new Matrix3d();
				Transform3D yRotationSide = new Transform3D();
				yRotationSide.rotY((float)Math.toRadians(90));
				yRotationSide.get(cameraRotationSide);
//				Transform3D xRotationSide = new Transform3D();
//				xRotationSide.rotX((float)Math.toRadians(0));
//				xRotationSide.mul(yRotationSide);
//				xRotationSide.get(cameraRotationSide);
			}
			vpTransform3D.setRotation(cameraRotationSide);
			break;
		case FRONT_VIEW:
			Matrix3d cameraRotationFront = null;
			if(reverseFrontView)
			{
				transVector.x = (float) boundsCenter.x;
				transVector.y = (float) boundsCenter.y - (radiusBS * 2.5f);
				transVector.z = (float) boundsCenter.z;

				vpTransform3D.setTranslation(transVector);

				cameraRotationFront = new Matrix3d();

				Transform3D xRotationFront = new Transform3D();
				xRotationFront.rotX((float)Math.toRadians(90));
				xRotationFront.get(cameraRotationFront);
			}
			else
			{
				transVector.x = (float) boundsCenter.x;
				transVector.y = (float) boundsCenter.y + (radiusBS * 2.5f);
				transVector.z = (float) boundsCenter.z;

				vpTransform3D.setTranslation(transVector);

				cameraRotationFront = new Matrix3d();
				Transform3D yRotationFront = new Transform3D();
				yRotationFront.rotY((float)Math.toRadians(180));
				Transform3D xRotationFront = new Transform3D();
				xRotationFront.rotX((float)Math.toRadians(90));
				xRotationFront.mul(yRotationFront);
				xRotationFront.get(cameraRotationFront);
			}
			vpTransform3D.setRotation(cameraRotationFront);
			break;
		}

		View view = universe.getCanvas().getView();
		view.setBackClipDistance(radiusBS * 1.5);

		orbitBehavior.setRotationCenter(boundsCenter);
		orbitBehavior.setHomeTransform(vpTransform3D);
		double cameraDistance = boundsCenter.distance(new Point3d(new double[]{transVector.x, transVector.y, transVector.z}));
		orbitBehavior.setTransFactors(cameraDistance * 0.1, cameraDistance * 0.1);

		orbitBehavior.goHome();
	}


	/**
	 * Prints the current SceneGraph
	 */
	//	public String printCurrentSceneGraph()
	//	{
	//		return UtilitaryMethods.printGroupInfo(groupRoot, 0);
	//	}

	@Override
	public void keyPressed(KeyEvent event) {
		switch(event.getKeyCode())
		{
		case KeyEvent.VK_S:
//			reverseFrontView =! reverseFrontView;
//			if(reverseFrontView)
//			{
//				System.out.println("Reverse Front view");
//			}
//			else
//			{
//				System.out.println("Front view");
//			}
			setView(FRONT_VIEW);
			break;
		case KeyEvent.VK_W:
			reverseTopView =! reverseTopView;
			if(reverseTopView)
			{
				System.out.println("Reverse Top view");
			}else
			{
				System.out.println("Top view");
			}
			setView(TOP_VIEW);
			break;
		case KeyEvent.VK_D:
			reverseSideView =! reverseSideView;
			if(reverseSideView)
			{
				System.out.println("Reverse Side view");
			}
			else
			{
				System.out.println("Side view");
			}
			setView(SIDE_VIEW);
			break;
		case KeyEvent.VK_Q:
			//			if(renderAxis)
			//			{
			//				gridResolution *= 10;
			//				renderAxis(renderAxis, gridResolution);
			//			}
			break;
		case KeyEvent.VK_SPACE:
			renderAxis =! renderAxis;
			renderAxis(groupRoot.getBounds());
//			drawShadow = !drawShadow;
//			drawShadow();
			break;

		case KeyEvent.VK_P:

			System.out.println("***************************************");
			//				System.out.println(printCurrentSceneGraph());
			System.out.println("***************************************");

			break;
		case KeyEvent.VK_ESCAPE:
			System.exit(0);
			break;
		}
	}

	/**
	 * Saves the position an orientation of the camera in a file
	 * @param viewPointFile file to write the camera transformation matrix
	 * @throws Exception
	 */
	public void saveViewpoint(File viewPointFile) throws Exception
	{
		String matrixString = "";
		MultiTransformGroup multiTG = orbitBehavior.getViewingPlatform().getMultiTransformGroup();
		for(int i = 0; i < multiTG.getNumTransforms(); i++)
		{
			TransformGroup tg = multiTG.getTransformGroup(i);
			Transform3D t1 = new Transform3D();
			double[] matrix = new double[16];
			tg.getTransform(t1);
			t1.get(matrix);
			for(int j = 0; j < 16; j++)
			{
				matrixString = matrixString.concat("matrix.pos." + j + "=" + matrix[j] + "\n");
			}
		}
		OutputStreamWriter fos = new OutputStreamWriter(new FileOutputStream(viewPointFile));
		fos.write(matrixString);
		fos.close();

	}

	@Override
	public void keyReleased(KeyEvent event) {

	}

	@Override
	public void keyTyped(KeyEvent event) {

	}
	

	public void mouseClicked(MouseEvent mouseEvent) {
		if(pickCanvas != null)
		{
			pickCanvas.setShapeLocation(mouseEvent);
			PickResult pickResult = pickCanvas.pickClosest();

			if (pickResult != null){
				//			System.out.println("Picked!!!");
				Shape3D pickedShape = (Shape3D)pickResult.getNode(PickResult.SHAPE3D);

				SceneGraphPath sceneGraphPath = pickResult.getSceneGraphPath();
				//Object userData = pickedShape.getUserData();
				if (pickedShape != null) {
					Object userData = pickedShape.getUserData();
					if(userData != null && userData instanceof Fiber)
					{
						Fiber f = (Fiber)userData;
						System.out.println(f.getFiberName() + " id: " + f.getFiberId() + " -- selected");
					}
				}
			}
		}
	}


	
	private void centerView(double newCenterX, double newCenterY, double newCenterZ)
	{
		ViewingPlatform viewingPlatform = universe.getViewingPlatform();

		//Get the transform group of the viewing platform
		TransformGroup vpTransformGroup = viewingPlatform.getViewPlatformTransform();
		Transform3D vpTransform3D = new Transform3D();	
		Vector3f transVector = new Vector3f();

		//Get the transformation vector from the transform3d of the transform group
		vpTransformGroup.getTransform(vpTransform3D);   
		vpTransform3D.get(transVector);

		Point3d boundsCenter = new Point3d(new double[]{newCenterX, newCenterY, newCenterZ});


		AxisAngle4f cameraRotationTop = null;

		double[] matrix = new double[16];
		vpTransform3D.get(matrix);

		transVector.x = (float) newCenterX;
		transVector.y = (float) newCenterY;
		transVector.z = (float) ((float) newCenterZ + (matrix[11]));

		vpTransform3D.setTranslation(transVector);
		cameraRotationTop = new AxisAngle4f(new Vector3f(0, 1, 0), (float)Math.toRadians(0));
		vpTransform3D.setRotation(cameraRotationTop);

		orbitBehavior.setRotationCenter(boundsCenter);
		orbitBehavior.setHomeTransform(vpTransform3D);
		double cameraDistance = boundsCenter.distance(new Point3d(new double[]{transVector.x, transVector.y, transVector.z}));
		orbitBehavior.setTransFactors(cameraDistance * 0.1, cameraDistance * 0.1);

		orbitBehavior.goHome();
	}


	@Override
	public void mouseEntered(MouseEvent arg0) {

	}

	@Override
	public void mouseExited(MouseEvent arg0) {

	}

	@Override
	public void mousePressed(MouseEvent arg0) {

	}

	@Override
	public void mouseReleased(MouseEvent arg0) {

	}


	/**
	 * Prints an image into a png file
	 * @param file file to be created
	 * @param imageSize size of the file (width/height)
	 */
	public void printImage(File file, int offScreenScale)
	{
		//		OffScreenCanvas3D offscreenCanvas = new OffScreenCanvas3D(SimpleUniverse.getPreferredConfiguration(), true, file);
		OffScreenCanvas3D offscreenCanvas = new OffScreenCanvas3D(graphicsConfiguration, true, file);
		offscreenCanvas.setName(OFFSCREEN_CANVAS_NAME);
		// set the offscreen to match the onscreen
		Screen3D sOn = canvas.getScreen3D();
		Screen3D sOff = offscreenCanvas.getScreen3D();

		Dimension imageSize = sOn.getSize();

		//		int offScreenScale = 3;

		imageSize.width *= offScreenScale;
		imageSize.height *= offScreenScale;
		sOff.setSize(imageSize);
		sOff.setPhysicalScreenWidth(sOn.getPhysicalScreenWidth() * offScreenScale);
		sOff.setPhysicalScreenHeight(sOn.getPhysicalScreenHeight() * offScreenScale);
		View view = universe.getViewer().getView();

		int numCanvas = view.numCanvas3Ds();
		if(numCanvas > 0)
		{
			for(int i = 0; i < numCanvas; i++)
			{
				if(view.getCanvas3D(i).getName().equals(OFFSCREEN_CANVAS_NAME))
				{
					view.removeCanvas3D(i);
				}
			}
		}
		view.addCanvas3D(offscreenCanvas);
		numCanvas = view.numCanvas3Ds();
		Dimension canvas3dSize = canvas.getSize();
		canvas3dSize.width *= offScreenScale;
		canvas3dSize.height *= offScreenScale;
		offscreenCanvas.doRender(canvas3dSize.width, canvas3dSize.height);
	}

	class OffScreenCanvas3D extends Canvas3D {

		File file;

		boolean printing = false;

		public OffScreenCanvas3D(GraphicsConfiguration gconfig,
				boolean offscreenflag, File file) {

			super(gconfig, offscreenflag);
			this.file = file;
		}

		void doRender(int width, int height) {

			BufferedImage bImage = new BufferedImage(width, height,
					BufferedImage.TYPE_INT_ARGB);

			ImageComponent2D buffer = new ImageComponent2D(
					ImageComponent.FORMAT_RGBA, bImage);

			setOffScreenBuffer(buffer);
			if(getOffScreenBuffer() != null)
			{
				try {
					Thread.sleep(100);
					renderOffScreenBuffer();
					waitForOffScreenRendering();
					bImage = getOffScreenBuffer().getImage();

					//------------------------------------------

					//---------------------------------------------

					boolean r =ImageIO.write(bImage, "png", file);
					if(r)
					{
						System.out.println(file.getName() + " saved");
					}
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			else
			{
				System.err.println("the file was not saved");
			}
		}

		public void postSwap() {

		}


	}


	/**
	 * Sets a transformation viewpoint for the camera
	 * @param viewpointTransform
	 */
	public void setViewpoint(Transform3D viewpointTransform) {
		orbitBehavior.setHomeTransform(viewpointTransform);
		orbitBehavior.goHome();

	}

	public void setGraphicsConfiguration(GraphicsConfiguration conf) {
		this.graphicsConfiguration = conf;		
	}

	public void setFrontView()
	{		
		setView(FRONT_VIEW);
	}

	public void setSideView()
	{		
		setView(SIDE_VIEW);
	}

	public void setTopView()
	{		
		setView(TOP_VIEW);
	}


	public void addBranchToExtras(BranchGroup newBranch) {
		groupExtra.addChild(newBranch);
	}

	public void removeBranchFromExtras(String name)
	{
		BranchGroup bg = null;
		Enumeration children = groupExtra.getAllChildren();
		while(children.hasMoreElements())
		{
			bg = (BranchGroup) children.nextElement();
			if(((String)bg.getUserData()).equalsIgnoreCase(name))
			{
				break;
			}
			else
			{
				bg = null;
			}
		}
		if(bg != null)
		{
			bg.detach();
		}
	}
	
	public void addFiberSet(FiberSetLite fiberSet)
	{
		if(!listBgFiberSet.containsKey(fiberSet.getName()))
		{
			BranchGroup bg = createFiberSet(fiberSet, listBgFiberSet.size());
			listBgFiberSet.put(fiberSet.getName(), bg);
			groupFibers.addChild(bg);
			updateSceneGraph();
		}
	}
	public void addVolume(Volume volume)
	{
		if(!listBgVolumes.containsKey(volume.getName()))
		{
			BranchGroup bg = createVolume(volume);
			listBgVolumes.put(volume.getName(), bg);
			groupVolumes.addChild(bg);
			updateSceneGraph();
		}
	}
	
//	private void createVolumes(ArrayList<Volume> volumes)
//	{
//		System.out.println("Creating volumes");
//		groupVolumes.detach();
//		for(Volume vol: volumes)
//		{
//			BranchGroup bg = createVolume(vol);
//			listBgVolumes.add(bg);
//			groupVolumes.addChild(bg);
//		}
//		groupRoot.addChild(groupVolumes);
//		System.out.println("End Creating volumes");
//	}

	private BranchGroup createVolume(Volume vol) {
		BranchGroup volume = new BranchGroup();
		NodeCharacteristics.setBranchGroupCapabilities(volume);
		
		ArrayList<double[]> points = vol.getPoints();
		ArrayList<int[]> triangleStrips = vol.getTriangleStrips();
		
		for(int[] tStrip : triangleStrips)
		{
			Point3d[] vertices = new Point3d[tStrip.length];
			int[] coordIndices = new int[tStrip.length];
			int verticesIndex = 0;
			for(int i = 0; i < vertices.length; i++)
			{
				coordIndices[i] = i;
				vertices[verticesIndex] = new Point3d(points.get(tStrip[i]));
				verticesIndex++;
			}
			
			GeometryInfo gi = new GeometryInfo( GeometryInfo.TRIANGLE_STRIP_ARRAY );
			gi.setCoordinates( vertices );
			gi.setCoordinateIndices( coordIndices);
			gi.setStripCounts(new int[]{vertices.length});

			NormalGenerator normalGenerator = new NormalGenerator();
			normalGenerator.generateNormals( gi );
			
			Appearance app = new Appearance();
			Material mat = new Material();
			Color3f col = new Color3f(vol.getColor());
			mat.setDiffuseColor(col);
			mat.setSpecularColor(col);
			mat.setShininess(0);
			app.setMaterial(mat);
			PolygonAttributes polAtt = new PolygonAttributes();
			polAtt.setPolygonMode(PolygonAttributes.POLYGON_FILL);
			app.setPolygonAttributes(polAtt);
			
			Shape3D triangleStripShape;
			try {
				triangleStripShape = new Shape3D(gi.getGeometryArray(), app);
				NodeCharacteristics.setShape3DCapabilites(triangleStripShape);
				triangleStripShape.setUserData(vol);
				volume.addChild(triangleStripShape);
			} catch (Exception e) {
				System.err.println("not rendering Triangle");
			}
		}
		return volume;
	}

//	public void createSceneGraph(ArrayList<FiberSetLite> fibers, ArrayList<Volume> volumes)
//	{
//		if(!fibers.isEmpty())
//		{
//			createFibers(fibers);
//		}
//		
//		if(!volumes.isEmpty())
//		{
//			createVolumes(volumes);
//		}
//		
//		updateLighting(groupRoot.getBounds());
//		BoundingSphere newBounds = (BoundingSphere) groupRoot.getBounds();
//		double boundRadius = newBounds.getRadius();
//		frontClip = boundRadius * frontClipDelta;
//		backClip = boundRadius * backClipDelta;
//		universe.getViewer().getView().setFrontClipDistance(frontClip);
//		universe.getViewer().getView().setBackClipDistance(backClip);
//		renderAxis(groupRoot.getBounds());
//		updateOrbitBehavior(groupRoot.getBounds());
//		setView(SIDE_VIEW);
//		pickCanvas = new PickCanvas(canvas, groupRoot);
//		pickCanvas.setMode(PickCanvas.GEOMETRY);
//		Point3d center = new Point3d();
//		newBounds.getCenter(center);
//		backgroundNode = new Background(BACKGROUND_COLOR);
//		BoundingSphere bckSphere = new BoundingSphere(center, newBounds.getRadius() * 10);
//		backgroundNode.setApplicationBounds(bckSphere);
//		groupBackground = new BranchGroup();
//		groupBackground.addChild(backgroundNode);
//		groupRoot.addChild(groupBackground);
//	}
	
	private void updateSceneGraph()
	{		
		updateLighting(groupRoot.getBounds());
		BoundingSphere newBounds = (BoundingSphere) groupRoot.getBounds();
		double boundRadius = newBounds.getRadius();
		frontClip = boundRadius * frontClipDelta;
		backClip = boundRadius * backClipDelta;
		universe.getViewer().getView().setFrontClipDistance(frontClip);
		universe.getViewer().getView().setBackClipDistance(backClip);
		renderAxis(groupRoot.getBounds());
		updateOrbitBehavior(groupRoot.getBounds());
		setView(SIDE_VIEW);
		pickCanvas = new PickCanvas(canvas, groupRoot);
		pickCanvas.setMode(PickCanvas.GEOMETRY);
		Point3d center = new Point3d();
		newBounds.getCenter(center);
		backgroundNode = new Background(BACKGROUND_COLOR);
		BoundingSphere bckSphere = new BoundingSphere(center, newBounds.getRadius() * 10);
		backgroundNode.setApplicationBounds(bckSphere);
		groupBackground = new BranchGroup();
		groupBackground.addChild(backgroundNode);
		groupRoot.addChild(groupBackground);
	}
	
	private BranchGroup createFiberSet(FiberSetLite fiberSet, int index)
	{
		BranchGroup bgFiberSet = new BranchGroup();
		NodeCharacteristics.setBranchGroupCapabilities(bgFiberSet);
		bgFiberSet.setUserData(GROUP_FIBER_SET_ + index);
		
		boolean[] fiberSetRendering = fiberSet.getRenderSetup();
		ArrayList<Fiber> fibers = fiberSet.getFibers();
		int fiberRenderCount = fiberSet.getFiberRenderCount();
		if(fiberRenderCount == 0)
		{
			fiberRenderCount = fibers.size();
		}
		if(fiberSetRendering[FiberSetLite.LINE])
		{
			Tube tube = new Tube();
//			int percentage = 0;
			System.out.println("Loading "+ fiberRenderCount+" fibers ");
			for(int i = 0; i < fiberRenderCount; i++)
			{
				//					bgLines.addChild(createFiberLine(fibers.get(i)));
				//					bgLines.addChild(createFiberLine(fibers.get(i)));
				try {
					bgFiberSet.addChild(tube.createTube(fibers.get(i), 0.5, 6, false));
				} catch (Exception e) {
					break;
				}
//				if((i + 1) % (fiberRenderCount/10) == 0)
//				{
//					percentage+=10;
//					System.out.println((i+1) + " fibers out of " + fiberRenderCount + " Added to scenegraph " + percentage + "%");
//				}
			}
			System.out.println("End loading fibers ");

		}
		if(fiberSetRendering[FiberSetLite.POINT])
		{
			for(int i = 0; i < fiberRenderCount; i++)
			{
				bgFiberSet.addChild(createFiberPoints(fibers.get(i), 10));
			}
		}
		if(fiberSetRendering[FiberSetLite.START_POINT])
		{
			for(int i = 0; i < fiberRenderCount; i++)
			{
				bgFiberSet.addChild(createFiberStartPoints(fibers.get(i), 10));
			}
		}
		if(fiberSetRendering[FiberSetLite.END_POINT])
		{
			for(int i = 0; i < fiberRenderCount; i++)
			{
				bgFiberSet.addChild(createFiberEndPoints(fibers.get(i), 10));
			}
		}		
		
		return bgFiberSet;
	}
	
	private BranchGroup createFiberPoints(Fiber fiber, int pointSize)
	{
		ArrayList<double[]> points = fiber.getPoints();
		Color fiberColor = fiber.getFiberColor();
		BranchGroup fiberPoints = new BranchGroup();
		NodeCharacteristics.setBranchGroupCapabilities(fiberPoints);
		fiberPoints.setUserData(fiber);

		if(points.size() > 0)
		{
			PointArray geometryRaw = new PointArray(points.size() , PointArray.COORDINATES);
			for(int i = 0; i < points.size(); i ++)
			{
				double[] p = points.get(i);
				((PointArray)geometryRaw).setCoordinate(i, new Point3d(p));
			}

			Appearance appearance = new Appearance();
			ColoringAttributes colAtt = new ColoringAttributes();
			colAtt.setColor(new Color3f(fiberColor));
			appearance.setColoringAttributes(colAtt);

			PointAttributes pointAtt = new PointAttributes(pointSize, true);
			appearance.setPointAttributes(pointAtt);

			Shape3D geometry = new Shape3D(geometryRaw, appearance);	
			geometry.setUserData(fiber);
			NodeCharacteristics.setShape3DCapabilites(geometry);
			fiberPoints.addChild(geometry);
		}

		return fiberPoints;
	}


	private BranchGroup createFiberStartPoints(Fiber fiber, int pointSize)
	{
		ArrayList<double[]> points = fiber.getPoints();

		BranchGroup startPoints = new BranchGroup();
		NodeCharacteristics.setBranchGroupCapabilities(startPoints);
		startPoints.setUserData(fiber);

		if(points.size() > 1)
		{
			PointArray geometryRaw = new PointArray(points.size() , PointArray.COORDINATES);

			double[] p = points.get(0);
			((PointArray)geometryRaw).setCoordinate(pointSize, new Point3d(p));


			Appearance appearance = new Appearance();
			ColoringAttributes colAtt = new ColoringAttributes();
			colAtt.setColor(new Color3f(Color.GREEN));
			appearance.setColoringAttributes(colAtt);

			PointAttributes pointAtt = new PointAttributes(pointSize, true);
			appearance.setPointAttributes(pointAtt);

			Shape3D geometry = new Shape3D(geometryRaw, appearance);	
			geometry.setUserData(fiber);
			NodeCharacteristics.setShape3DCapabilites(geometry);
			startPoints.addChild(geometry);

		}

		return startPoints;
	}

	private BranchGroup createFiberEndPoints(Fiber fiber, int pointSize)
	{
		ArrayList<double[]> points = fiber.getPoints();
		BranchGroup	endPoints = new BranchGroup();
		NodeCharacteristics.setBranchGroupCapabilities(endPoints);
		endPoints.setUserData(fiber);

		if(points.size() > 1)
		{
			PointArray geometryRaw = new PointArray(points.size() , PointArray.COORDINATES);

			double[] p = points.get(points.size() - 1);
			((PointArray)geometryRaw).setCoordinate(0, new Point3d(p));


			Appearance appearance = new Appearance();
			ColoringAttributes colAtt = new ColoringAttributes();
			colAtt.setColor(new Color3f(Color.RED));
			appearance.setColoringAttributes(colAtt);

			PointAttributes pointAtt = new PointAttributes(pointSize, true);
			appearance.setPointAttributes(pointAtt);

			Shape3D geometry = new Shape3D(geometryRaw, appearance);	
			geometry.setUserData(fiber);
			NodeCharacteristics.setShape3DCapabilites(geometry);
			endPoints.addChild(geometry);
		}

		return endPoints;
	}

	
	public void removeFiberSet() {

		
	}
	
	


	public Canvas3D getCanvas() {
		return canvas;
	}
}
